#!/usr/bin/env python
# -*- coding: utf-8 -*-

import selectors,threading,socket,logging,datetime

"""
FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

# 回调函数,自己定义形参
def accept(sock:socket.socket,mask):
    # mask: 事件掩码的或值
    conn,raddr = sock.accept()
    conn.setblocking(False) # 不阻塞
    # 监视conn这个文件对象
    key = selector.register(conn,selectors.EVENT_READ,read)
    logging.info(key)

# 回调函数
def read(conn:socket.socket,mask):
    data = conn.recv(1024)
    msg = "Your msg is {}".format(data.decode())
    conn.send(msg.encode())

# 构造缺省性能最优selector
selector = selectors.DefaultSelector()

# 创建TCP Server
sock = socket.socket()
sock.bind(('127.0.0.1',9999))
sock.listen()
logging.info(sock)

sock.setblocking(False) # 非阻塞

# 注册文件对象sock关注读事件,返回SelectorKey
# 将sock,关注事件,data都绑定到key实例属性上.
key = selector.register(sock,selectors.EVENT_READ,accept)
logging.info(key)

e = threading.Event()

def select(e):
    while not e.is_set():
        # 开始监视,等到有文件对象监控事件产生,返回(key,mask)元组.
        events = selector.select()
        print('-'*30)
        for key,mask in events:
            logging.info(key)
            logging.info(mask)
            callback = key.data # 回调函数
            callback(key.fileobj,mask)

threading.Thread(target=select,args=(e,),name = 'select').start()

def main():
    while not e.is_set():
        cmd = input('>>>')
        if cmd.strip() == 'quit':
            e.set()
            fobjs = []
            logging.info('{}'.format(list(selector.get_map().items())))
            for fd,key in selector.get_map().items(): # 返回注册的项
                print(fd,key)
                print(key.fileobj)
                fobjs.append(key.fileobj)

            for fobj in fobjs:
                selector.unregister(fobj)
                fobj.close() # 关闭socket
            selector.close()


if __name__ == '__main__':
    main()
"""




# 练习
# 将ChatServer改写成IO多路复用的方式
# 不需要启动多线程来执行socket的accept,recv方法.
"""
FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class ChatServer:
    def __init__(self,ip='127.0.0.1',port=9999):
        self.sock = socket.socket()
        self.addr = (ip,port)

        self.event = threading.Event()
        self.selector = selectors.DefaultSelector() # 创建selector

    def start(self): # 启动监听
        self.sock.bind(self.addr) # 绑定.
        self.sock.listen() # 监听
        self.sock.setblocking(False) # 不阻塞
        # 注册
        self.selector.register(self.sock,selectors.EVENT_READ,self.accept)

        threading.Thread(target=self.select,name='selector',daemon=True).start()

    def select(self): # 阻塞
        while not self.event.is_set():
            # 开始监视,等到某文件对象被监控的事件产生,返回(key,mask)元组.
            events = self.selector.select() # 阻塞,直到events
            print('-'*30) 
            for key,mask in events:
                logging.info(key)
                logging.info(mask)
                callback = key.data # 回调函数
                callback(key.fileobj)

    def accept(self,sock:socket.socket): # 接收客户端连接
        conn,raddr = sock.accept()        
        conn.setblocking(False) # 非阻塞
        # 注册,监视每一个与客户端的连接的socket对象.
        self.selector.register(conn,selectors.EVENT_READ,self.recv)

    def recv(self,sock:socket.socket): # 接收客户端数据
        data = sock.recv(1024)
        if not data or data == b'quit': # 客户端主动断开或退出,注销并关闭socket。
            self.selector.unregister(sock)
            sock.close()
            return 
        msg = "{:%Y/%m/%d %H:%M:%S} {}:{}\n{}\n".format(datetime.datetime.now(),*sock.getpeername(),data.decode())
        logging.info(msg)
        msg = msg.encode()
        # 群发
        for key in self.selector.get_map().values():
            if key.data == self.recv: # 排除self.accept
                key.fileobj.send(msg)

    
    def stop(self): # 停止服务
        self.event.set()
        fobjs = []
        for fd,key in self.selector.get_map().items():
            fobjs.append(key.fileobj)
        for fobj in fobjs:
            self.selector.unregister(fobj)
            fobj.close()
        self.selector.close()


def main():
    cs = ChatServer()
    cs.start()

    while True:
        cmd = input('>>>').strip()
        if cmd == 'quit':
            cs.stop()
            threading.Event().wait(3)
            break
        logging.info(threading.enumerate())

if __name__=='__main__':
    main()
"""



import socket,threading,datetime,logging,selectors
from queue import Queue

FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class ChatServer:
    def __init__(self,ip='127.0.0.1',port=9999):
        self.sock = socket.socket()
        self.addr = (ip,port)
        self.clients = {}
        self.event = threading.Event()
        self.selector = selectors.DefaultSelector() # 创建selector

    def start(self): # 启动监听
        self.sock.bind(self.addr) # 绑定
        self.sock.listen() # 监听
        self.sock.setblocking(False) # 不阻塞

        # 注册
        self.selector.register(self.sock,selectors.EVENT_READ,self.accept)
        threading.Thread(target=self.select,name='selector',daemon=True).start()

    def select(self): # 阻塞
        while not self.event.is_set():
            # 开始监视,等到某文件对象被监控的事件发生,返回(key,mask)元组.
            events = self.selector.select() # 阻塞,直到events

            for key,mask in events:
                if callable(key.data):
                    callback = key.data # key对象的data属性,回调.
                    callback(key.fileobj,mask)
                else:
                    callback = key.data[0]
                    callback(key,mask)

    def accept(self,sock:socket.socket,mask): # 接收客户端连接
        conn,raddr = sock.accept()
        conn.setblocking(False) # 非阻塞
        self.clients[raddr] = (self.handle,Queue())

        # 注册,监视每一个与客户端的连接的socket对象.
        self.selector.register(conn,selectors.EVENT_READ | selectors.EVENT_WRITE,self.clients[raddr])

    def handle(self,key: selectors.SelectorKey,mask): # 接收客户端数据

        if mask & selectors.EVENT_READ:
            sock = key.fileobj
            raddr = sock.getpeername()
            data = sock.recv(1024)

            if not data or data == b'quit':
                self.selector.unregister(sock)
                sock.close()
                self.clients.pop(raddr)
                return 

            msg = "{:%Y/%m/%d %H:%M:%S} {}:{}\n{}\n".format(datetime.datetime.now(),*raddr,data.decode())

            logging.info(msg)
            msg = msg.encode()

            for k in self.selector.get_map().values():
                logging.info(k)
                if isinstance(k.data,tuple):
                    k.data[1].put(data)

        if mask & selectors.EVENT_WRITE:
            # 因为写一直就绪,mask为2,所以一直可以写,从而导致select()不断循环,如同不阻塞一样.
            if not key.data[1].empty():
                key.fileobj.send(key.data[1].get())

    
    def stop(self): # 停止服务
        self.event.set()
        fobjs = []
        for fd,key in self.selector.get_map().items():
            fobjs.append(key.fileobj)
        for fobj in fobjs:
            self.selector.unregister(fobj)
            fobj.close()
        self.selector.close()


def main():
    cs = ChatServer()
    cs.start()

    while True:
        cmd = input('>>>').strip()
        if cmd == 'quit':
            cs.stop()
            threading.Event().wait(3)
            break
        logging.info(threading.enumerate())
        logging.info('-'*30)
        logging.info("{} {}".format(len(cs.clients),cs.clients))
        logging.info(list(map(lambda x: (x.fileobj.fileno(),x.data),cs.selector.get_map().values())))
        logging.info('-'*30)

if __name__=="__main__":
    main()